package com.liuqi.openai.keyword;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author liuqi
 * @date 2025/8/3
 **/
public class SimilarityFuzzyMatchKeywordExtractor extends ExactMatchKeywordExtractor {

    private static final int SIMILARITY_MINI_MATCH_LENGTH = 3;

    /**
     * 相似度最少要匹配多少字符数, 默认值 {@link #SIMILARITY_MINI_MATCH_LENGTH}, 不能小于 {@link #SIMILARITY_MINI_MATCH_LENGTH}
     *
     * 否则输入空字符串也能匹配到关键词
     */
    private final int similarityMiniMatch;

    /**
     * 模糊匹配的最大编辑距离, 默认值 1
     */
    private final int maxEditDistance;

    /**
     * 匹配到第一个关键词后, 退出模糊匹配, 默认值 false
     */
    private final boolean matchedExitSearch;

    /**
     * 是否优先精确匹配, 如果精确匹配到关键词, 将不会触发模糊匹配
     * 默认值: true
     */
    private final boolean priorityExactMatch;

    public SimilarityFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries) {
        this(dictionaries, null, SIMILARITY_MINI_MATCH_LENGTH, 1, false, true);
    }

    public SimilarityFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                                int maxEditDistance) {
        this(dictionaries, null, SIMILARITY_MINI_MATCH_LENGTH, maxEditDistance, false, true);
    }

    public SimilarityFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                                int similarityMiniMatch,
                                                int maxEditDistance) {
        this(dictionaries, null, similarityMiniMatch, maxEditDistance, false, true);
    }

    public SimilarityFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                                int similarityMiniMatch,
                                                int maxEditDistance,
                                                boolean matchedExitSearch) {
        this(dictionaries, null, similarityMiniMatch, maxEditDistance, matchedExitSearch, true);
    }

    public SimilarityFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                                Set<String> stopWords,
                                                int similarityMiniMatch,
                                                int maxEditDistance,
                                                boolean matchedExitSearch,
                                                boolean priorityExactMatch) {
        super(dictionaries, stopWords);
        if (similarityMiniMatch < SIMILARITY_MINI_MATCH_LENGTH) {
            throw new IllegalArgumentException("the minimum number of matching characters for similarity is less than : " + SIMILARITY_MINI_MATCH_LENGTH);
        }
        this.similarityMiniMatch = similarityMiniMatch;
        this.maxEditDistance = maxEditDistance;
        this.matchedExitSearch = matchedExitSearch;
        this.priorityExactMatch = priorityExactMatch;
    }

    @Override
    protected MatchResult match(String text, int pos) {
        // 优先精确匹配, 如果匹配到关键词, 则不再进行模糊匹配
        if (priorityExactMatch) {
            MatchResult result = super.match(text, pos);
            if (result != null) {
                return result;
            }
        }

        return similarityFuzzyMatch(text, pos);
    }

    /**
     * 相似度模糊匹配
     *
     * @param text  检索文本
     * @param pos   检索文本长度偏移量
     * @return
     */
    private MatchResult similarityFuzzyMatch(String text, int pos) {
        // 匹配到的关键词结果
        List<String> keywords = new ArrayList<>(2);

        // 最大匹配文本长度
        int maxMatchLength = 0;

        // // 检索次数
        int searchCount = 0;

        // 由FuzzyMatchState进行相似度匹配
        Deque<FuzzyMatchState> stack = new ArrayDeque<>();
        stack.push(new FuzzyMatchState(root, pos, "", 0));

        while (!stack.isEmpty()) {
            searchCount++;  // 检索次数

            // 当前模糊匹配
            FuzzyMatchState currSearch = stack.pop();

            // 当前模糊匹配节点
            TrieNode currNode = currSearch.node;

            // 检索到关键词结尾了
            if (currNode.endOfWord) {
                // 相似度长度
                int matchLen = currSearch.offset - pos;
                // 相似度长度 需要 >= 最小相似度长度
                // 相似度长度 + 最大编辑长度 需要 >= 关键词长度
                if (matchLen >= similarityMiniMatch && matchLen + maxEditDistance >= currSearch.currentWord.length()) {
                    // 最大匹配文本长度
                    maxMatchLength = Math.max(maxMatchLength, matchLen);

                    // 添加匹配到的关键词
                    keywords.add(currSearch.currentWord);

                    // 匹配到结果, 立即跳出循环
                    if (matchedExitSearch) {
                        break;
                    }
                }
            }

            // 检索文档到最后一个字符
            if (currSearch.offset >= text.length()) {
                // 可编辑距离还有剩余
                if (currSearch.edits < maxEditDistance) {
                    // 将子类的 key 都进行拼接
                    pushSearchStateFromChildren(stack, currSearch, currNode, null);
                }
                continue;
            }

            // 当前检索文本的关键字
            char key = Character.toLowerCase(text.charAt(currSearch.offset));

            // 如果当前可编辑长度还有剩余, 将子节点的所有关键字进行拼接, 进行下一轮检索
            if (currSearch.edits < maxEditDistance) {
                pushSearchStateFromChildren(stack, currSearch, currNode, key);
            }

            // 如果子节点中包含 当前检索文本的关键字
            // 文本检索的offset + 1
            // 编辑距离不变
            if (currNode.children.containsKey(key)) {
                stack.push(new FuzzyMatchState(
                        currNode.children.get(key),
                        currSearch.offset + 1,
                        currSearch.currentWord + key,
                        currSearch.edits
                ));
            }

        }

        if (keywords.isEmpty()) {
            return null;
        }

        return new MatchResult(keywords, maxMatchLength, searchCount, true);
    }

    /**
     * 将<code>node</code>的子节点添加到<code>stack</code>上
     *
     * @param stack
     * @param search
     * @param node
     */
    private void pushSearchStateFromChildren(Deque<FuzzyMatchState> stack, FuzzyMatchState search, TrieNode node, Character matchKey) {
        for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) {
            // 子节点的关键字 不等于 matchKey时, 全部添加到待检索队列
            if (matchKey == null || entry.getKey().charValue() != matchKey.charValue()) {
                // 将子类的 key 都进行拼接, 进行下一轮模糊检索
                // 文本检索的不变
                // 编辑距离 + 1
                stack.push(new FuzzyMatchState(
                        // 检索关键词节点
                        entry.getValue(),
                        // 检索文本偏移量
                        search.offset,
                        // 模糊搜索, 追加key
                        search.currentWord + entry.getKey(),
                        // 编辑距离
                        search.edits + 1
                ));
            }
        }
    }

}
